import cv2
import numpy as np
import random
import heapq

# 游戏参数
width, height = 300, 300  # 地图范围
snake_size = 10  # 蛇身和食物的大小
fps = 180  # 移动速度

# 初始化游戏状态
snake = [(100, 100), (90, 100), (80, 100)]  # 初始蛇身位置
direction = 'RIGHT'
food = (random.randint(0, width // snake_size - 1) * snake_size,
        random.randint(0, height // snake_size - 1) * snake_size)

def draw_snake_and_food(frame):
    # 绘制蛇的身体
    for i, (x, y) in enumerate(snake):
        color = (0, 255, 0) if i > 0 else (255, 0, 0)  # 蛇的身体用绿色，头部用红色
        cv2.rectangle(frame, (x, y), (x + snake_size, y + snake_size), color, -1)
    
    # 绘制食物
    cv2.rectangle(frame, (food[0], food[1]), (food[0] + snake_size, food[1] + snake_size), (0, 0, 255), -1)

def move_snake(new_head):
    global food
    if new_head == food:
        snake.insert(0, new_head)
        food = (random.randint(0, width // snake_size - 1) * snake_size,
                random.randint(0, height // snake_size - 1) * snake_size)
    else:
        snake.insert(0, new_head)
        snake.pop()

def check_collision(new_head):
    x, y = new_head
    if x < 0 or x >= width or y < 0 or y >= height:
        print(f"Game over: Snake hit the wall at position {new_head}")
        return True
    if new_head in snake:
        print(f"Game over: Snake collided with itself at position {new_head}. Current snake positions: {snake}")
        return True
    return False

def heuristic(a, b):
    return abs(a[0] - b[0]) + abs(a[1] - b[1])

def a_star_search(start, target, obstacles):
    open_set = []
    heapq.heappush(open_set, (0, start, []))
    g_score = {start: 0}
    f_score = {start: heuristic(start, target)}
    came_from = {}

    while open_set:
        _, current, path = heapq.heappop(open_set)

        if current == target:
            return path + [current]

        for dx, dy in [(-snake_size, 0), (snake_size, 0), (0, -snake_size), (0, snake_size)]:
            neighbor = (current[0] + dx, current[1] + dy)
            tentative_g_score = g_score[current] + 1

            if 0 <= neighbor[0] < width and 0 <= neighbor[1] < height and neighbor not in obstacles:
                if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_g_score
                    f_score[neighbor] = tentative_g_score + heuristic(neighbor, target)
                    heapq.heappush(open_set, (f_score[neighbor], neighbor, path + [current]))

    return None

def expose_food():
    # 尝试所有可能的方向，直到找到一个可行的路径
    directions = [(-snake_size, 0), (snake_size, 0), (0, -snake_size), (0, snake_size)]
    random.shuffle(directions)  # 随机化方向，避免陷入死循环
    for dx, dy in directions:
        new_head = (snake[0][0] + dx, snake[0][1] + dy)
        if 0 <= new_head[0] < width and 0 <= new_head[1] < height and new_head not in snake:
            return [new_head]
    return None

def can_expose_food(snake, food):
    # 检查食物是否被蛇包围
    snake_set = set(snake)
    for dx, dy in [(-snake_size, 0), (snake_size, 0), (0, -snake_size), (0, snake_size)]:
        neighbor = (food[0] + dx, food[1] + dy)
        if neighbor not in snake_set and 0 <= neighbor[0] < width and 0 <= neighbor[1] < height:
            return True  # 找到可以暴露食物的缺口
    return False  # 食物被完全包围

def draw_path(frame, path):
    if path:
        for point in path:
            # 绘制灰色实线框，大小与蛇头一致
            cv2.rectangle(frame, (point[0], point[1]), (point[0] + snake_size, point[1] + snake_size), (128, 128, 128), 1)

def show_message(message, frame=None, font_scale=1, thickness=2, wait_key=True):
    if frame is None:
        frame = np.zeros((height, width, 3), dtype=np.uint8)
    text_size = cv2.getTextSize(message, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness)[0]
    text_x = (width - text_size[0]) // 2
    text_y = (height + text_size[1]) // 2
    cv2.putText(frame, message, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), thickness)
    cv2.imshow('Snake Game', frame)
    if wait_key:
        cv2.waitKey(0)

# 显示“按任意键开始”
show_message("Press any key to start the game...", font_scale=0.8, thickness=2)

# 主循环
game_over = False
last_frame = None  # 用于保存最后一帧的画面
while not game_over:
    # 创建空白画布
    frame = np.zeros((height, width, 3), dtype=np.uint8)

    # 绘制蛇和食物
    draw_snake_and_food(frame)

    # 如果食物出现在蛇身上，或者食物被包围，允许蛇继续走
    if food in snake or not can_expose_food(snake, food):
        path = expose_food()  # 暴露食物路径
        if path is not None:
            new_head = path[0]
            if not check_collision(new_head):  # 确保不撞墙或撞到自己
                move_snake(new_head)
            else:
                game_over = True  # 如果无法移动，游戏结束
        else:
            game_over = True  # 如果无法暴露食物，游戏结束
    else:
        # 计算新的路径
        path = a_star_search(snake[0], food, snake)

        if path is None or len(path) < 2:
            # 检查食物是否被包围
            if can_expose_food(snake, food):
                # 继续向前走暴露食物
                new_head = (snake[0][0] + (snake[1][0] - snake[0][0]), snake[0][1] + (snake[1][1] - snake[0][1]))
                if not check_collision(new_head):  # 确保不撞墙或撞到自己
                    move_snake(new_head)
                    path = expose_food()  # 继续寻找食物的路径
                else:
                    game_over = True  # 如果无法移动，游戏结束
            else:
                # 如果没有路径且食物被完全包围，游戏结束
                game_over = True
                last_frame = frame.copy()
                print(f"Game over: Food is completely surrounded and no path found. Food position: {food}")
                break
        else:
            # 计算新头部位置
            new_head = path[1]

            # 检查碰撞
            if check_collision(new_head):
                game_over = True
                last_frame = frame.copy()
                break

            # 移动蛇
            move_snake(new_head)

    # 绘制路径
    draw_path(frame, path)

    # 显示目标点的坐标和当前的蛇身长度
    food_text = f"Food: ({food[0]}, {food[1]})"
    length_text = f"Length: {len(snake)}"

    # 调整字体位置和大小（减少字体大小）
    font_scale = 0.5  # 小字体
    thickness = 1  # 更细的字体

    # 在右上角显示食物坐标
    cv2.putText(frame, food_text, (width - 150, 30), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), thickness)

    # 在右上角显示蛇的长度
    cv2.putText(frame, length_text, (width - 150, 60), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), thickness)

    # 显示帧
    cv2.imshow('Snake Game', frame)

    # 处理键盘输入
    key = cv2.waitKey(int(1000 / fps)) & 0xFF
    if key == ord('q'):
        break

# 保存最后一帧的画面
if last_frame is not None:
    cv2.imwrite('last_frame.png', last_frame)

# 显示最后一帧的画面
if last_frame is not None:
    show_message("Game Over! Press any key to exit...", frame=last_frame, font_scale=0.8, thickness=2, wait_key=True)

# 等待按键释放所有窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

